package com.example.android.sampleapp.test.support;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.BooleanNode;
import org.codehaus.jackson.node.ContainerNode;
import org.codehaus.jackson.node.DoubleNode;
import org.codehaus.jackson.node.IntNode;
import org.codehaus.jackson.node.LongNode;
import org.codehaus.jackson.node.ObjectNode;
import org.codehaus.jackson.node.TextNode;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
/**
* A simple class for programmatic editing of JSON strings.
* <p/>
* Construct with a JSON string. The JSON string should represent an array or object.
* <p/>
* Traverse the JSON string's tree with child(index), child(key), parent(), and root() to change the
* node which is currently focused by the editor. Most methods operate on the currently focused node.
* After calling child() on an instance, don't forget to use parent() or root() to traverse back up
* if you want to make edits to other branches of the tree.
* <p/>
* If the focused node is an array, edit it with set(index, value), append(value), insert(index, value),
* remove(index), and removeAll(). Get its length with length().
* <p/>
* If the focused node is an object, edit it with set(key, value), put(key, value), remove(key),
* and removeAll(). Get all of its keys with keySet().
* <p/>
* Add sub-trees into the JSON tree by passing another JsonEditor to the editing methods.
* The whole tree (or simply the value node) represented by the other JsonEditor's currently
* focused node will be added.
* <p/>
* For focused object nodes and array nodes, convert them back to JSON with toJson().
* <p/>
* For focused value nodes, use the valueAsXXX() methods to get the value, and isNullValue()
* to check if its value is null.
* <p/>
* For any focused node, isArray() and isObject() will report the data type of the focused node.
* <p/>
* All edit and traversal methods support method chaining to let you walk the tree and edit along the way,
* all in the same line of code. Getting carried away with this can make unreadable code.
* <p/>
* Because this class was designed to be used in unit tests for things like tweaking JSON fixture
* data within a test, it is strict and throws exceptions whenever something unexpected happens.
*/
public class JsonEditor {
private JsonNode rootNode;
private JsonNode focusedNode;
private LinkedList<JsonNode> focusStack = new LinkedList<JsonNode>();
private LinkedList<String> toStringStack = new LinkedList<String>();
public JsonEditor(String jsonString) {
rootNode = parseObjectOrArrayJson(jsonString);
focusedNode = rootNode;
}
public JsonEditor root() {
focusedNode = rootNode;
focusStack.clear();
toStringStack.clear();
return this;
}
public JsonEditor child(int index) {
assertNodeIsArrayNodeAndHasIndex(index);
focusStack.push(focusedNode);
toStringStack.push(Integer.toString(index));
focusedNode = focusedNode.get(index);
return this;
}
public JsonEditor child(String propertyName) {
assertNodeIsObjectNodeAndHasProperty(propertyName);
focusStack.push(focusedNode);
toStringStack.push("'" + propertyName + "'");
focusedNode = focusedNode.get(propertyName);
return this;
}
public JsonEditor parent() {
if (focusStack.isEmpty()) {
throw new RootNodeHasNoParent();
}
focusedNode = focusStack.pop();
toStringStack.pop();
return this;
}
public String toJson() {
if (!focusedNode.isContainerNode()) {
throw new NotAnArrayOrObjectNodeException();
}
return focusedNode.toString();
}
/**
* Debugging aid.
*
* @return String which shows path to currently focused node.
*/
public String toString() {
String s = "JsonEditor{focus=ROOT";
Iterator<String> iterator = toStringStack.descendingIterator();
while (iterator.hasNext()) {
s += "[" + iterator.next() + "]";
}
return s + "}";
}
public JsonEditor set(String propertyName, JsonEditor newValueFromCurrentPositionOfEditor) {
return setValueOfObjectProperty(propertyName, parseJson(newValueFromCurrentPositionOfEditor.focusedNode.toString()));
}
public JsonEditor set(String propertyName, int newValue) {
return setValueOfObjectProperty(propertyName, new IntNode(newValue));
}
public JsonEditor set(String propertyName, long newValue) {
return setValueOfObjectProperty(propertyName, new LongNode(newValue));
}
public JsonEditor set(String propertyName, double newValue) {
return setValueOfObjectProperty(propertyName, new DoubleNode(newValue));
}
public JsonEditor set(String propertyName, boolean newValue) {
return setValueOfObjectProperty(propertyName, booleanNodeForValue(newValue));
}
public JsonEditor set(String propertyName, String newValue) {
return setValueOfObjectProperty(propertyName, new TextNode(newValue));
}
public JsonEditor put(String propertyName, JsonEditor newValueFromCurrentPositionOfEditor) {
return putValueOfObjectProperty(propertyName, parseJson(newValueFromCurrentPositionOfEditor.focusedNode.toString()));
}
public JsonEditor put(String propertyName, int newValue) {
return putValueOfObjectProperty(propertyName, new IntNode(newValue));
}
public JsonEditor put(String propertyName, long newValue) {
return putValueOfObjectProperty(propertyName, new LongNode(newValue));
}
public JsonEditor put(String propertyName, double newValue) {
return putValueOfObjectProperty(propertyName, new DoubleNode(newValue));
}
public JsonEditor put(String propertyName, boolean newValue) {
return putValueOfObjectProperty(propertyName, booleanNodeForValue(newValue));
}
public JsonEditor put(String propertyName, String newValue) {
return putValueOfObjectProperty(propertyName, new TextNode(newValue));
}
public JsonEditor set(int index, JsonEditor newValueFromCurrentPositionOfEditor) {
return setAtArrayIndex(index, parseJson(newValueFromCurrentPositionOfEditor.focusedNode.toString()));
}
public JsonEditor set(int index, int newValue) {
return setAtArrayIndex(index, new IntNode(newValue));
}
public JsonEditor set(int index, long newValue) {
return setAtArrayIndex(index, new LongNode(newValue));
}
public JsonEditor set(int index, double newValue) {
return setAtArrayIndex(index, new DoubleNode(newValue));
}
public JsonEditor set(int index, boolean newValue) {
return setAtArrayIndex(index, booleanNodeForValue(newValue));
}
public JsonEditor set(int index, String newValue) {
return setAtArrayIndex(index, new TextNode(newValue));
}
public JsonEditor append(JsonEditor newValueFromCurrentPositionOfEditor) {
return appendToArray(parseJson(newValueFromCurrentPositionOfEditor.focusedNode.toString()));
}
public JsonEditor append(int newValue) {
return appendToArray(new IntNode(newValue));
}
public JsonEditor append(long newValue) {
return appendToArray(new LongNode(newValue));
}
public JsonEditor append(double newValue) {
return appendToArray(new DoubleNode(newValue));
}
public JsonEditor append(boolean newValue) {
return appendToArray(booleanNodeForValue(newValue));
}
public JsonEditor append(String newValue) {
return appendToArray(new TextNode(newValue));
}
public JsonEditor insert(int atIndex, JsonEditor newValueFromCurrentPositionOfEditor) {
return insertAtArrayIndex(atIndex, parseJson(newValueFromCurrentPositionOfEditor.focusedNode.toString()));
}
public JsonEditor insert(int atIndex, int newValue) {
return insertAtArrayIndex(atIndex, new IntNode(newValue));
}
public JsonEditor insert(int atIndex, long newValue) {
return insertAtArrayIndex(atIndex, new LongNode(newValue));
}
public JsonEditor insert(int atIndex, double newValue) {
return insertAtArrayIndex(atIndex, new DoubleNode(newValue));
}
public JsonEditor insert(int atIndex, boolean newValue) {
return insertAtArrayIndex(atIndex, booleanNodeForValue(newValue));
}
public JsonEditor insert(int atIndex, String newValue) {
return insertAtArrayIndex(atIndex, new TextNode(newValue));
}
public JsonEditor removeAll() {
if (focusedNode.isContainerNode()) {
((ContainerNode) focusedNode).removeAll();
} else {
throw new NotAnArrayOrObjectNodeException();
}
return this;
}
public JsonEditor remove(int index) {
assertNodeIsArrayNodeAndHasIndex(index);
((ArrayNode) focusedNode).remove(index);
return this;
}
public JsonEditor remove(String propertyName) {
((ObjectNode) focusedNode).remove(propertyName);
return this;
}
public Number valueAsNumber() {
if (focusedNode.isNumber()) {
return focusedNode.getNumberValue();
} else {
throw new NotANumericNodeException();
}
}
public boolean valueAsBoolean() {
if (focusedNode.isBoolean()) {
return focusedNode.getBooleanValue();
} else {
throw new NotABooleanNodeException();
}
}
public String valueAsString() {
if (focusedNode.isTextual()) {
return focusedNode.getTextValue();
} else {
throw new NotAStringNodeException();
}
}
public boolean isNullValue() {
return focusedNode.isNull();
}
public boolean isArray() {
return focusedNode.isArray();
}
public boolean isObject() {
return focusedNode.isObject();
}
public int length() {
assertNodeIsArrayNode();
return focusedNode.size();
}
public Set<String> keySet() {
assertNodeIsObjectNode();
HashSet<String> keys = new HashSet<String>();
Iterator<String> iterator = focusedNode.getFieldNames();
while (iterator.hasNext()) {
keys.add(iterator.next());
}
return keys;
}
public static class JsonEditorException extends RuntimeException {
public JsonEditorException() {
}
public JsonEditorException(Throwable throwable) {
super(throwable);
}
public JsonEditorException(String message) {
super(message);
}
}
public static class NotABooleanNodeException extends JsonEditorException {
}
public static class NotANumericNodeException extends JsonEditorException {
}
public static class NotAStringNodeException extends JsonEditorException {
}
public static class NotAnArrayOrObjectNodeException extends JsonEditorException {
}
public static class NotAnArrayNodeException extends JsonEditorException {
}
public static class NotAnObjectNodeException extends JsonEditorException {
}
public static class NoSuchPropertyException extends JsonEditorException {
}
public static class RootNodeHasNoParent extends JsonEditorException {
}
private void assertNodeIsArrayNode() {
if (!focusedNode.isArray()) {
throw new NotAnArrayNodeException();
}
}
private void assertNodeIsArrayNodeAndHasIndex(int index) {
assertNodeIsArrayNode();
if (!focusedNode.has(index)) {
throw new ArrayIndexOutOfBoundsException();
}
}
private void assertNodeIsObjectNode() {
if (!focusedNode.isObject()) {
throw new NotAnObjectNodeException();
}
}
private void assertObjectNodeHasProperty(String propertyName) {
if (!focusedNode.has(propertyName)) {
throw new NoSuchPropertyException();
}
}
private void assertNodeIsObjectNodeAndHasProperty(String propertyName) {
assertNodeIsObjectNode();
assertObjectNodeHasProperty(propertyName);
}
private JsonEditor setAtArrayIndex(int index, JsonNode newNode) {
assertNodeIsArrayNodeAndHasIndex(index);
((ArrayNode) focusedNode).set(index, newNode);
return this;
}
private JsonEditor insertAtArrayIndex(int index, JsonNode newNode) {
if (index < 0) {
throw new ArrayIndexOutOfBoundsException("negative index: " + index);
}
if (index > 0) {
assertNodeIsArrayNodeAndHasIndex(index);
} else {
assertNodeIsArrayNode();
}
((ArrayNode) focusedNode).insert(index, newNode);
return this;
}
private JsonEditor appendToArray(JsonNode newNode) {
assertNodeIsArrayNode();
((ArrayNode) focusedNode).add(newNode);
return this;
}
private JsonEditor putValueOfObjectProperty(String propertyName, JsonNode newNode) {
assertNodeIsObjectNode();
putPropertyOnObjectNode(propertyName, newNode);
return this;
}
private JsonEditor setValueOfObjectProperty(String propertyName, JsonNode newNode) {
assertNodeIsObjectNodeAndHasProperty(propertyName);
putPropertyOnObjectNode(propertyName, newNode);
return this;
}
private void putPropertyOnObjectNode(String propertyName, JsonNode newNode) {
((ObjectNode) focusedNode).put(propertyName, newNode);
}
private BooleanNode booleanNodeForValue(boolean newValue) {
return newValue ? BooleanNode.TRUE : BooleanNode.FALSE;
}
private JsonNode parseObjectOrArrayJson(String jsonString) {
JsonNode root = parseJson(jsonString);
if (!root.isContainerNode()) {
throw new JsonEditorException("jsonString must be a JSON array or object");
}
return root;
}
private JsonNode parseJson(String jsonString) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonString, JsonNode.class);
} catch (IOException e) {
throw new JsonEditorException(e);
}
}
}